using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

using System;
using System.Reflection;
using GameTypes;

using DebugLibrary;
using Camera;

using StillDesign.PhysX;

namespace Physics
{
	public class PhysicsService
	{

		private int ObjectCount = 0;
		internal System.Collections.Generic.Dictionary<int,GameObject> objects = new Dictionary<int,GameObject>();
		internal List<int> newObjects = new List<int>();

		private PhysicsService()
		{
			BoxShapeDescription boxShapeDesc = new BoxShapeDescription(2, 3, 8);
		}

		internal static PhysicsService getPhysicsService()
		{
			return new PhysicsService();
		}

		public int addObject(GameObject Object)
		{
			objects.Add(ObjectCount, Object);
			newObjects.Add(ObjectCount);
			return ObjectCount++;
		}

		public void removeObject(int Index)
		{
			if (this.newObjects.Contains(Index)) this.newObjects.Remove(Index);
			this.objects.Remove(Index);
		}

		public void removeObject(GameObject Object)
		{
			var item = objects.First(kvp => kvp.Value == Object);
			this.removeObject(item.Key);
		}

	}
#if DEBUG
	public class Physics : DrawableGameComponent
#else
	public class Physics : GameComponent
#endif
	{

		private Core PhysXCore;
		private Scene PhysXScene;
		private Game game;
		private PhysicsService service;

		Actor ObjectActor;

#if DEBUG
		DrawApi3D draw3DApi;
		BasicEffect basicEffect;
		CameraService cameraService;
#endif

		ControllerManager controllerManager;

		public Physics(Game game) : base(game)
		{
			this.game = game;
			this.service = PhysicsService.getPhysicsService();
			this.game.Services.AddService(typeof(PhysicsService), this.service);
		}

		public override void Initialize()   
		{

			System.Console.Out.WriteLine("Initializing component \"" + this.GetType().Name + "\"...");

			// TODO: Add your initialization logic here
			CoreDescription coreDesc = new CoreDescription();
			UserOutput output = new UserOutput();

			this.PhysXCore = new Core(coreDesc, output);

			Core core = this.PhysXCore;

			#if DEBUG
			core.SetParameter(PhysicsParameter.VisualizationScale, 2.0f);
			core.SetParameter(PhysicsParameter.VisualizeCollisionShapes, true);
			core.SetParameter(PhysicsParameter.VisualizeClothMesh, true);
			core.SetParameter(PhysicsParameter.VisualizeJointLocalAxes, true);
			core.SetParameter(PhysicsParameter.VisualizeJointLimits, true);
			core.SetParameter(PhysicsParameter.VisualizeFluidPosition, true);
			core.SetParameter(PhysicsParameter.VisualizeFluidEmitters, false); // Slows down rendering a bit too much
			core.SetParameter(PhysicsParameter.VisualizeForceFields, true);
			core.SetParameter(PhysicsParameter.VisualizeSoftBodyMesh, true);

			draw3DApi = (DrawApi3D)this.game.Services.GetService(typeof(DrawApi3D));
			cameraService = (CameraService)this.game.Services.GetService(typeof(CameraService));
			#endif

			SimulationType simulationType = (core.HardwareVersion == HardwareVersion.None ? SimulationType.Software : SimulationType.Hardware);

			SceneDescription sceneDesc = new SceneDescription()
			{
				//SimulationType = SimulationType.Hardware,
				SimulationType = simulationType,
				Gravity = new Vector3(0, -9.81f, 0).AsPhysX(),
				GroundPlaneEnabled = true
			};
			
			this.PhysXScene = core.CreateScene(sceneDesc);


			controllerManager = PhysXScene.CreateControllerManager();
			
			
			CapsuleControllerDescription desc = new StillDesign.PhysX.CapsuleControllerDescription(5, 10);
			desc.Position = new StillDesign.PhysX.MathPrimitives.Vector3(0, 10, 0);
			desc.ClimbingMode = StillDesign.PhysX.CapsuleClimbingMode.Easy;
			desc.StepOffset = 0.5f;
			desc.SkinWidth = 0.1f;
			desc.UpDirection = StillDesign.PhysX.Axis.X;
			desc.InteractionFlag = StillDesign.PhysX.CCTInteractionFlag.Include;
			StillDesign.PhysX.CapsuleController controller = controllerManager.CreateController<StillDesign.PhysX.CapsuleController>(desc);
			controller.SetCollisionEnabled(true);
			controller.Actor.UserData = controller;
			ObjectActor = controller.Actor;



			
			
			//ControllerManager manager = PhysXScene.CreateControllerManager();
			/*
			CapsuleControllerDescription capsuleControllerDesc = new CapsuleControllerDescription(4, 3);

			CapsuleController capsuleController = controllerManager.CreateController<CapsuleController>(capsuleControllerDesc);
			capsuleController.Position = new StillDesign.PhysX.MathPrimitives.Vector3(0, 1.5f + 2, -15);
			capsuleController.Actor.Name = "BoxController";
			capsuleController.SetCollisionEnabled(true);
			*/

			
			HardwareVersion ver = PhysXCore.HardwareVersion;
			SimulationType simType = this.PhysXScene.SimulationType;

			Console.Out.WriteLine("PhysX: Acceleration Type: " + simType);

			#if DEBUG
			// Connect to the remote debugger if it's there
			core.Foundation.RemoteDebugger.Connect("localhost");
			#endif

			base.Initialize();

			System.Console.Out.WriteLine("Coomponent  \"" + this.GetType().Name + "\" initialized");

		}

	   

		public override void Update(GameTime gameTime)
		{

#if DEBUG
			basicEffect.View = this.cameraService.getView();
			basicEffect.Projection = this.cameraService.getProjection();
#endif

			PhysicsService service = (PhysicsService)game.Services.GetService(typeof(PhysicsService));


			ObjectActor.GlobalPose = Matrix.CreateTranslation(cameraService.getCameraTarget() + new Vector3(0, 10, 0)).AsPhysX();

			foreach (int newObjectIndex in this.service.newObjects)
			{

				GameObject Object = this.service.objects[newObjectIndex];

				ConvexMeshDescription triangleMeshDesc = ModelConvertor.getConvexMeshDescription(Object.getModel());



				ConvexMesh convexMesh;
				using (MemoryStream s = new MemoryStream())
				{
					Cooking.InitializeCooking();
					Cooking.CookConvexMesh(triangleMeshDesc, s);
					Cooking.CloseCooking();

					s.Position = 0;
					convexMesh = PhysXScene.Core.CreateConvexMesh(s);
				}

				ConvexShapeDescription convexShapeDesc = new ConvexShapeDescription(convexMesh);

				MaterialDescription md = new MaterialDescription();
				md.DynamicFriction = 0.5f;
				md.Restitution = 0.01f;
				md.StaticFriction = 0.5f;

				Material m = this.PhysXScene.CreateMaterial(md);
				convexShapeDesc.Material = m;



				ActorDescription actorDesc = new ActorDescription()
				{
					BodyDescription = new BodyDescription(100.0f)
				};
				actorDesc.Shapes.Add(convexShapeDesc);
				
				

				/*
				TriangleMesh triangleMesh;
				using (MemoryStream s = new MemoryStream())
				{
					Cooking.InitializeCooking();
					Cooking.CookTriangleMesh(triangleMeshDesc, s);
					Cooking.CloseCooking();

					s.Position = 0;
					triangleMesh = PhysXScene.Core.CreateTriangleMesh(s);
				}


				
				TriangleMeshShapeDescription triangleMeshShapeDesc = new TriangleMeshShapeDescription()
				{
					TriangleMesh = triangleMesh,
					Flags = ShapeFlag.Visualization
				};
				
				ActorDescription actorDesc = new ActorDescription()
				{
					Name = Object.getName(),
					BodyDescription = new BodyDescription(1000.0f),
					GlobalPose = StillDesign.PhysX.MathPrimitives.Matrix.Translation(0, 0, 0),
					Shapes = { triangleMeshShapeDesc }
				};
				*/
				Actor actor = PhysXScene.CreateActor(actorDesc);
				actor.GlobalPosition = Object.getPoosition().AsPhysX();
				actor.GlobalOrientation = Object.getOrientation().AsPhysX();
				actor.UserData = newObjectIndex;

			}

			this.service.newObjects.Clear();

			this.PhysXScene.Simulate((float)gameTime.ElapsedGameTime.TotalSeconds);
			this.PhysXScene.FlushStream();
			this.PhysXScene.FetchResults(SimulationStatus.RigidBodyFinished, true);

			foreach (Actor a in this.PhysXScene.Actors)
			{
				if (a.UserData != null)
				{
					GameObject obj = service.objects[(int)a.UserData];
					obj.setPosition(a.GlobalPosition.AsXna());
					obj.setOrientation(a.GlobalOrientation.AsXna());
				}
			}
			
		}

#if DEBUG
		protected override void LoadContent()
		{
			basicEffect = new BasicEffect(GraphicsDevice);
			basicEffect.VertexColorEnabled = true;
		}

		protected override void UnloadContent()
		{
			controllerManager.Dispose();
			base.UnloadContent();
		}

		public override void Draw(GameTime gameTime)
		{

			basicEffect.CurrentTechnique.Passes[0].Apply();

			foreach (DebugLine line in this.PhysXScene.GetDebugRenderable().GetDebugLines())
			{
				draw3DApi.DrawLine(line.Point0.AsXna(), line.Point1.AsXna(), ColorConverter.getXnaColor(line.Color));
			}

		}
#endif

	}

	public static class ColorConverter
	{

		public static Color getXnaColor(int PhysXColor)
		{

			byte a = (byte)((PhysXColor & 0xFF000000) >> 32);
			byte r = (byte)((PhysXColor & 0x00FF0000) >> 16);
			byte g = (byte)((PhysXColor & 0x0000FF00) >> 8);
			byte b = (byte)((PhysXColor & 0x000000FF) >> 0);

			return new Color(r, g, b, a);
		}

	}

	public class UserOutput : UserOutputStream
	{
		public override void Print(string message)
		{
			Console.WriteLine("PhysX: " + message);
		}
		public override AssertResponse ReportAssertionViolation(string message, string file, int lineNumber)
		{
			Console.WriteLine("PhysX: " + message);
			return AssertResponse.Continue;
		}
		public override void ReportError(ErrorCode errorCode, string message, string file, int lineNumber)
		{
			Console.WriteLine("PhysX: " + message);
		}
	}

	public static class MathExtension
	{

		public static StillDesign.PhysX.MathPrimitives.Vector3 AsPhysX(this Microsoft.Xna.Framework.Vector3 vector)
		{
			return new StillDesign.PhysX.MathPrimitives.Vector3(vector.X, vector.Y, vector.Z);
		}

		public static Microsoft.Xna.Framework.Vector3 AsXna(this StillDesign.PhysX.MathPrimitives.Vector3 vector)
		{
			return new Microsoft.Xna.Framework.Vector3(vector.X, vector.Y, vector.Z);
		}

		public static StillDesign.PhysX.MathPrimitives.Matrix AsPhysX(this Microsoft.Xna.Framework.Matrix m)
		{
			return new StillDesign.PhysX.MathPrimitives.Matrix
			(
				m.M11, m.M12, m.M13, m.M14,
				m.M21, m.M22, m.M23, m.M24,
				m.M31, m.M32, m.M33, m.M34,
				m.M41, m.M42, m.M43, m.M44
			);
		}

		public static Microsoft.Xna.Framework.Matrix AsXna(this StillDesign.PhysX.MathPrimitives.Matrix m)
		{
			return new Microsoft.Xna.Framework.Matrix
			(
				m.M11, m.M12, m.M13, m.M14,
				m.M21, m.M22, m.M23, m.M24,
				m.M31, m.M32, m.M33, m.M34,
				m.M41, m.M42, m.M43, m.M44
			);
		}

	}

	

}

